/* Declarations for the runtime interpreter.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KP_INTERP__
#define __KP_INTERP__   1

#include "defs.hpp"
#include "event.hpp"

KP_DECLS_BEGIN

struct interpreter;

// Valid hook types.
enum
{
  HOOK_TYPE_STKMOV,
  HOOK_TYPE_FCALL,
  HOOK_TYPE_LAST
};

struct interp_hook
{
  unsigned int type;
  void (*cb) (interpreter *, void *);
  void *arg;
  interpreter *interp = nullptr;
  dlist link;

  interp_hook (unsigned int tp,
      void (*cb) (interpreter *, void *), void *arg = nullptr) :
      type (tp), cb (cb), arg (arg)
    {
    }

  bool attach (interpreter *interp);

  bool detach ();

  void call (interpreter *interp)
    {
      this->cb (interp, this->arg);
    }

  ~interp_hook ()
    {
      this->detach ();
    }
};

// Reference to a GC-visible value.
struct valref
{
  dlist link;
  object value;

  valref () = delete;
  valref (const valref&) = delete;

  inline valref (interpreter *interp, object val = 0);
  inline valref (object val = 0);

  valref (valref&& right)
    {
      this->link = right.link;
      this->value = right.value;
      right.link.init_head ();
    }

  object& operator* ()
    {
      return (this->value);
    }

  object operator* () const
    {
      return (this->value);
    }

  ~valref ()
    {
      this->link.del ();
    }
};

struct sync_event;
struct memmgr;
struct coroutine;
struct stream;
struct io_info;
struct pack_info;
struct comparator;

// Possible interpreter states.
enum
{
  INTERP_RUNNING,
  INTERP_BLOCKING,
  INTERP_SUSPENDED
};

struct stack_allocator
{
  char *curr;
  uint32_t left = 0;

  void init (char *ptr, uint32_t size)
    {
      this->curr = ptr;
      this->left = size;
    }

  char* alloc (uint32_t size)
    {
      size = (size + sizeof (std::max_align_t)) &
        ~(alignof (std::max_align_t) - 1);

      if (size >= this->left)
        return (nullptr);

      char *ret = this->curr;
      this->curr += size;
      this->left -= size;
      return (ret);
    }

  void reset (char *ptr)
    {
      this->left += (uint32_t)(this->curr - ptr);
      this->curr = ptr;
    }
};

// Temporary buffer.
struct interp_tbuf
{
  union
    {
      interp_tbuf *next;
      std::max_align_t align;
    };
};

struct interpreter
{
  object *stack;
  object *stkend;
  uint32_t cur_frame;
  uint32_t throw_frame;
  uint32_t exc_offset;
  object thread;
  object last_err;
  object last_tb;
  object stkobj;
  object xpkg;
  // Interpreter registers.
  object retval;
  object alval;
  object aux;
  // Current and saved interpreter state.
  int state;
  int saved_state;
  stack_allocator stk_alloc;
  bool evh_active;
  bool exc_raised;
#if !defined (__GNUC__)
  bool result_errored;
#endif
  interp_tbuf *tbuf;
  sync_event *evp;
  uint32_t rand_seed;
  uint32_t call_depth;
  uint32_t npendev;
  intptr_t pendev_mask[NPENDEV / (sizeof (intptr_t) * 8) +
                       (NPENDEV % (sizeof (intptr_t) * 8) != 0)];
  dlist values;
  memmgr *mmgr;
  unsigned int num_hooks[HOOK_TYPE_LAST];
  dlist hooks;
  object *tl_syms;
  uintptr_t n_tlsyms;
#ifdef KP_PLATFORM_WINDOWS
  void *io_event;
#endif

  // Internal constants.
  enum
    {
      frame_size = 5
    };

  bool init (char *base, uint32_t size);

  void reset_exc ()
    {
      this->throw_frame = 0;
      this->exc_raised = false;
      this->last_err = this->last_tb = NIL;
    }

  result<void> growstk (uint32_t off);

  result<object*> push (object elem)
    {
      KP_VTRY (this->growstk (1));
      *this->stkend++ = elem;
      return (this->stkend - 1);
    }

  object pop ()
    {
      return (this->retval = *--this->stkend);
    }

  uint32_t stklen () const
    {
      return ((uint32_t)(this->stkend - this->stack));
    }

  void popn (uint32_t n = 1)
    {
      this->stkend -= n;
    }

  object stktop () const
    {
      return (*(this->stkend - 1));
    }

  void* talloc (size_t size);

  void do_call_hooks (unsigned int type, unsigned int n);
  void call_hooks (unsigned int type)
    {
      unsigned int n = this->num_hooks[type];
      if (kp_unlikely (n))
        this->do_call_hooks (type, n);
    }

#ifdef KP_NO_THREADS
  bool lock ()
    {
      return (false);
    }

  void unlock (bool = true)
    {
    }

  bool lock_remote (interpreter *)
    {
      return (false);
    }

#else
  bool lock ();
  void unlock (bool release = true);
  bool lock_remote (interpreter *interp);
#endif

  result<void> begin_blocking ();
  void end_blocking ();

  struct lock_guard
    {
      interpreter *interp;
      bool rel;

      lock_guard (interpreter *ip) : interp (ip)
        {
          this->rel = this->interp->lock ();
        }

      ~lock_guard ()
        {
          this->interp->unlock (this->rel);
        }
    };

#ifdef KP_NO_THREADS
#  define KP_TLS_INTERP
#elif defined (__clang__) && (defined (__MINGW32__) || defined (__MINGW64__))
#  define KP_TLS_INTERP   __thread
#else
#  define KP_TLS_INTERP   thread_local
#endif

  static KP_TLS_INTERP interpreter *self_interp;

  static interpreter* self () __attribute__ ((const))
    {
      return (interpreter::self_interp);
    }

  static void set_self (interpreter *interp)
    {
      interpreter::self_interp = interp;
    }

  sync_event*& sync_ev ()
    {
      return (this->evp);
    }

  object caller () const;

  uint32_t xrand ();

  void nargs_msg (char *buf, int size, int min_argc,
                  int max_argc, int passed);

  exception raise (object exception);
  exception raise (result<object> exc)
    {
      if (exc.error_p ())
        return (exception ());
      return (this->raise (*exc));
    }

  exception raise (const char *exctype, const char *msg);

  exception raise (const char *exctype, object msg);
  exception raise (const char *exctype, result<object> msg)
    {
      if (msg.error_p ())
        return (exception ());
      return (this->raise (exctype, *msg));
    }

  exception raise_nargs (object name, int min, int max, int passed);
  exception raise_nargs (int min, int max, int passed)
    {
      return (this->raise_nargs (this->caller (), min, max, passed));
    }

  exception raise_oob (int idx, int max);

  exception raise_const ()
    {
      return (this->raise ("const-error", "cannot modify read-only object"));
    }

  result<bool> exc_handle (void);
  result<bool> push_frame (object env, int nargs, int off);

  result<object> dbind_idx (uintptr_t tl_idx, object value);
  result<object> dbind (object sym, object value);
  void unbind (uint32_t n);
  result<bool> unbind (uint32_t n, coroutine *crp);

  result<object> stacktrace (uint32_t frame, uint32_t limit = 0);

  void set_ev (unsigned int ev);
  result<void> do_handle_evs ();

  result<void> handle_evs ()
    {
      if (kp_unlikely (this->npendev && this->evh_active))
        {
          lock_guard g (this);
          return (this->do_handle_evs ());
        }

      return (0);
    }

#if !defined (__GNUC__)

  template <typename T>
  T store_result (T&& rx)
    {
      this->result_errored = error_p (rx);
      return (rx);
    }

#endif

  // User friendly interface for hooks.
  struct hook
    {
      interp_hook base;

      virtual void call (interpreter *) = 0;

      static void hook_cb (interpreter *interp, void *arg)
        {
          ((hook *)arg)->call (interp);
        }

      bool attach (interpreter *interp)
        {
          return (this->base.attach (interp));
        }

      bool detach ()
        {
          return (this->base.detach ());
        }

      hook (unsigned int tp) : base (tp, hook_cb, this) {}
    };
};

struct tmp_allocator
{
  interpreter *ip;
  interp_tbuf *tbuf;
  char *stk_top;

  tmp_allocator (interpreter *interp) : ip (interp),
      tbuf (interp->tbuf), stk_top (interp->stk_alloc.curr)
    {
    }

  void* slow_alloc (uint32_t size);
  void talloc_cleanup ();

  void* alloc (uint32_t size)
    {
      void *ret = this->ip->stk_alloc.alloc (size);
      if (kp_unlikely (!ret))
        ret = this->slow_alloc (size);
      return (ret);
    }

  ~tmp_allocator ()
    {
      this->ip->stk_alloc.reset (this->stk_top);
      if (kp_unlikely (this->tbuf != this->ip->tbuf))
        this->talloc_cleanup ();
    }
};

valref::valref (interpreter *interp, object val)
{
  this->value = val;
  interp->values.add (&this->link);
}

valref::valref (object val) : valref (interpreter::self (), val)
{
}

// For event management.
struct evh_guard
{
  bool *swap;

  evh_guard (bool *p = nullptr) : swap (p)
    {
    }

  static result<evh_guard>
  make (interpreter *interp)
    {
      evh_guard ret (nullptr);
      if (interp->evh_active)
        {
          *(ret.swap = &interp->evh_active) = false;
          KP_VTRY (interp->handle_evs ());
        }

      return (ret);
    }

  ~evh_guard ()
    {
      if (this->swap)
        *this->swap = true;
    }
};

struct evh_safeguard
{
  bool prev;
  interpreter *ip;

  evh_safeguard (interpreter *interp) : prev (interp->evh_active), ip (interp)
    {
      interp->evh_active = false;
    }

  ~evh_safeguard ()
    {
      this->ip->evh_active = prev;
    }
};

// Saves and restores the stack pointer.
struct sp_guard
{
  interpreter *interp;
  uint32_t sp;

  sp_guard (interpreter *interp, uint32_t sp) :
      interp (interp), sp (sp)
    {
    }

  sp_guard (interpreter *interp) : sp_guard (interp, interp->stklen ())
    {
    }

  ~sp_guard ()
    {
      this->interp->stkend = this->interp->stack + this->sp;
    }
};

// Saves what's needed in function calls.
struct call_guard : public sp_guard
{
  uint32_t cf;
  uint32_t eoff;

  call_guard (interpreter *interp, uint32_t spadj = 0) :
      sp_guard (interp, interp->stklen () - spadj),
      cf (interp->cur_frame), eoff (interp->exc_offset)
    {
    }

  ~call_guard ()
    {
      this->interp->cur_frame = this->cf;
      this->interp->exc_offset = this->eoff;
    }
};

// Temporarily binds a dynamic symbol.
struct dbinding
{
  interpreter *interp;
  valref val;
  uintptr_t tl_idx;

  dbinding (interpreter *ip) : interp (ip), val (ip), tl_idx (0)
    {
    }

  result<void> init (uintptr_t idx, object val);

  ~dbinding ();
};

struct wait_point
{
  interpreter *interp;

  wait_point (interpreter *ip) : interp (ip)
    {
    }

  result<void> begin ()
    {
      return (this->interp->begin_blocking ());
    }

  ~wait_point ()
    {
      this->interp->end_blocking ();
    }
};

// Main thread's interpreter.
KP_EXPORT interpreter *main_interp;

KP_DECLS_END

#endif
